package com.zenchn.aliyun.oss.api.impl;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.*;
import com.zenchn.aliyun.oss.api.OssUploadService;
import com.zenchn.aliyun.oss.utils.OssCommonUtils;
import com.zenchn.entity.JsonResult;
import org.springframework.stereotype.Service;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;


/**
 * oss上传 实现类
 * @author dingxinfa
 * @date 2018-08-16
 *
 */
@Service
public class OssUploadServiceImpl implements OssUploadService {


	@Override
	public JsonResult<Map> simplyUploadByBytes(String endpoint, String accessKeyId, String accessKeySecret, String bucketName, String objectName, byte[] bytes) {

		JsonResult jsonResult=new JsonResult();
		jsonResult.setSuccess(false);
		jsonResult.setCode(0);

		OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);

		try{
			String md5 = BinaryUtil.toBase64String(BinaryUtil.calculateMd5(bytes));
			ObjectMetadata meta = new ObjectMetadata();
			meta.setContentMD5(md5);

			ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes),meta);

			ObjectMetadata objectMeta = ossClient.getObjectMetadata(bucketName, objectName);
			Map<String, Object> data = new HashMap<>();
			data.put("contentType", objectMeta.getContentType());
			data.put("contentLength", objectMeta.getContentLength());
			data.put("lastModified", objectMeta.getLastModified());
			jsonResult.setData(data);

			jsonResult.setSuccess(true);
			jsonResult.setCode(1);

			ossClient.shutdown();

		} catch (OSSException oe) {
			jsonResult.setData(OssCommonUtils.extractOSSException(oe));

		} catch (ClientException ce) {
			jsonResult.setData(OssCommonUtils.extractClientException(ce));
		} finally {
			//关闭客服端释放资源
			if (ossClient != null) {
				ossClient.shutdown();
			}
		}
		return jsonResult;
	}

	@Override
	public JsonResult<Map> simplyUploadByUrl(String endpoint, String accessKeyId, String accessKeySecret, String bucketName, String objectName, String url) {

		JsonResult jsonResult=new JsonResult();
		jsonResult.setSuccess(false);
		jsonResult.setCode(0);

		OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);
		try{
			// 上传网络流。
			InputStream inputStream = new URL(url).openStream();

			ossClient.putObject(bucketName, objectName, inputStream);

			ObjectMetadata objectMeta = ossClient.getObjectMetadata(bucketName, objectName);
			Map<String, Object> data = new HashMap<>();
			data.put("contentType", objectMeta.getContentType());
			data.put("contentLength", objectMeta.getContentLength());
			data.put("lastModified", objectMeta.getLastModified());
			jsonResult.setData(data);

			jsonResult.setSuccess(true);
			jsonResult.setCode(1);

			ossClient.shutdown();
		} catch (OSSException oe) {
			jsonResult.setData(OssCommonUtils.extractOSSException(oe));

		} catch (ClientException ce) {
			jsonResult.setData(OssCommonUtils.extractClientException(ce));
		} catch (MalformedURLException e) {
			jsonResult.setData(OssCommonUtils.extractMalformedURLException(e));
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			//关闭客服端释放资源
			if (ossClient != null) {
				ossClient.shutdown();
			}
		}
		return jsonResult;
	}

	@Override
	public JsonResult<Map> simplyUploadByFile(String endpoint, String accessKeyId, String accessKeySecret, String bucketName, String objectName, String localFilePath) {
		return simplyUploadByFile(endpoint, accessKeyId, accessKeySecret, bucketName, objectName, new File(localFilePath));
	}

	@Override
	public JsonResult<Map> simplyUploadByFile(String endpoint, String accessKeyId, String accessKeySecret, String bucketName, String objectName, File localFile) {
		JsonResult jsonResult=new JsonResult();
		OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);
		try{
			String md5 = BinaryUtil.toBase64String(BinaryUtil.calculateMd5(OssCommonUtils.getBytes(localFile)));

			ObjectMetadata meta = new ObjectMetadata();
			meta.setContentMD5(md5);

			ossClient.putObject(bucketName, objectName, localFile,meta);

			ObjectMetadata objectMeta = ossClient.getObjectMetadata(bucketName, objectName);
			Map<String, Object> data = new HashMap<>();
			data.put("contentType", objectMeta.getContentType());
			data.put("contentLength", objectMeta.getContentLength());
			data.put("lastModified", objectMeta.getLastModified());
			jsonResult.setData(data);

			jsonResult.setSuccess(true);
			jsonResult.setCode(1);

			ossClient.shutdown();
		} catch (OSSException oe) {
			jsonResult.setData(OssCommonUtils.extractOSSException(oe));
			jsonResult.setSuccess(false);
			jsonResult.setCode(0);
		} catch (ClientException ce) {
			jsonResult.setData(OssCommonUtils.extractClientException(ce));
			jsonResult.setSuccess(false);
			jsonResult.setCode(0);
		} finally {
			//关闭客服端释放资源
			if (ossClient != null) {
				ossClient.shutdown();
			}
		}
		return jsonResult;
	}

	@Override
	public JsonResult<Map> appendUploadByInputStream(String endpoint, String accessKeyId, String accessKeySecret, String bucketName, String objectName, String contentType, InputStream inputStream) {
		JsonResult jsonResult=new JsonResult();
		jsonResult.setSuccess(false);
		jsonResult.setCode(0);

		OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);
		try{
			boolean found = ossClient.doesObjectExist(bucketName, objectName);
			if (found){
				// 存在追加。

				// 获取文件的部分元信息。
				SimplifiedObjectMeta objectMeta = ossClient.getSimplifiedObjectMeta(bucketName, objectName);

				// 通过AppendObjectRequest设置多个参数。
				AppendObjectRequest appendObjectRequest = new AppendObjectRequest(bucketName, objectName, inputStream);

				// nextPosition指明下一次请求中应当提供的Position，即文件当前的长度。
				appendObjectRequest.setPosition(objectMeta.getSize());
				appendObjectRequest.setInputStream(inputStream);
				AppendObjectResult appendObjectResult = ossClient.appendObject(appendObjectRequest);
			}else{
				//第一次追加
				ObjectMetadata meta = new ObjectMetadata();
				// 指定上传的内容类型。
				meta.setContentType(contentType);

				AppendObjectRequest appendObjectRequest = new AppendObjectRequest(bucketName, objectName, inputStream,meta);

				appendObjectRequest.setPosition(0L);
				AppendObjectResult appendObjectResult = ossClient.appendObject(appendObjectRequest);
			}

			ObjectMetadata objectMeta = ossClient.getObjectMetadata(bucketName, objectName);
			Map<String, Object> data = new HashMap<>();
			data.put("contentType", objectMeta.getContentType());
			data.put("contentLength", objectMeta.getContentLength());
			data.put("lastModified", objectMeta.getLastModified());
			jsonResult.setData(data);

			jsonResult.setSuccess(true);
			jsonResult.setCode(1);

			// 关闭OSSClient。
			ossClient.shutdown();
		} catch (OSSException oe) {
			jsonResult.setData(OssCommonUtils.extractOSSException(oe));
		} catch (ClientException ce) {
			jsonResult.setData(OssCommonUtils.extractClientException(ce));
		} finally {
			//关闭客服端释放资源
			if (ossClient != null) {
				ossClient.shutdown();
			}
		}
		return jsonResult;
	}

	@Override
	public JsonResult<Map> breakpointUploadByFile(String endpoint, String accessKeyId, String accessKeySecret, String bucketName, String objectName, String contentType, String localFilePath, String callbackUrl) {
		JsonResult jsonResult=new JsonResult();
		jsonResult.setSuccess(false);
		jsonResult.setCode(0);

		OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);
		try {

			String checkpointFile = localFilePath + ".log";

			String md5 = BinaryUtil.toBase64String(BinaryUtil.calculateMd5(OssCommonUtils.getBytes(new File(localFilePath))));
			ObjectMetadata meta = new ObjectMetadata();
			// 指定上传的内容类型。
			meta.setContentType(contentType);
			// 设置MD5校验。
			meta.setContentMD5(md5);
			// 通过UploadFileRequest设置多个参数。

			UploadFileRequest uploadFileRequest = new UploadFileRequest(bucketName,objectName);

			uploadFileRequest.setUploadFile(localFilePath);
			// 指定上传并发线程数，默认为1。
			uploadFileRequest.setTaskNum(5);
			// 指定上传的分片大小，范围为100KB~5GB，默认为文件大小/10000。
			uploadFileRequest.setPartSize(1 * 1024 * 1024);
			// 开启断点续传，默认关闭。
			uploadFileRequest.setEnableCheckpoint(true);
			// 记录本地分片上传结果的文件。开启断点续传功能时需要设置此参数，上传过程中的进度信息会保存在该文件中，如果某一分片上传失败，再次上传时会根据文件中记录的点继续上传。上传完成后，该文件会被删除。默认与待上传的本地文件同目录，为uploadFile.ucp。
			uploadFileRequest.setCheckpointFile(checkpointFile);
			// 文件的元数据。
			uploadFileRequest.setObjectMetadata(meta);

			if (null != callbackUrl){;
				// 设置上传成功回调，参数为Callback类型。
				uploadFileRequest.setCallback(OssCommonUtils.buildCallback(callbackUrl,objectName));
			}

			// 断点续传上传。
			ossClient.uploadFile(uploadFileRequest);



			ObjectMetadata objectMeta = ossClient.getObjectMetadata(bucketName, objectName);
			Map<String, Object> data = new HashMap<>();
			data.put("contentType", objectMeta.getContentType());
			data.put("contentLength", objectMeta.getContentLength());
			data.put("lastModified", objectMeta.getLastModified());
			jsonResult.setData(data);

			jsonResult.setSuccess(true);
			jsonResult.setCode(1);

			// 关闭OSSClient。
			ossClient.shutdown();
		} catch (OSSException oe) {
			jsonResult.setData(OssCommonUtils.extractOSSException(oe));

		} catch (ClientException ce) {
			jsonResult.setData(OssCommonUtils.extractClientException(ce));
		} catch (MalformedURLException e) {
			jsonResult.setData(OssCommonUtils.extractMalformedURLException(e));
		} catch (Throwable throwable) {
			jsonResult.setData(OssCommonUtils.extractThrowable(throwable));
		} finally {
			//关闭客服端释放资源
			if (ossClient != null) {
				ossClient.shutdown();
			}
		}
		return jsonResult;


	}

	@Override
	public JsonResult<Map> multipartUploadByFile(String endpoint, String accessKeyId, String accessKeySecret, String bucketName, String objectName, String localFilePath, String callbackUrl) {
		return multipartUploadByFile(endpoint, accessKeyId, accessKeySecret, bucketName, objectName, new File(localFilePath),callbackUrl);
	}

	@Override
	public JsonResult<Map> multipartUploadByFile(String endpoint, String accessKeyId, String accessKeySecret, String bucketName, String objectName, File localFile, String callbackUrl) {
		JsonResult jsonResult=new JsonResult();
		jsonResult.setSuccess(false);
		jsonResult.setCode(0);

		OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);
		try{
			/* 步骤1：初始化一个分片上传事件。*/
			InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectName);
			InitiateMultipartUploadResult result = ossClient.initiateMultipartUpload(request);
			// 返回uploadId，它是分片上传事件的唯一标识，您可以根据这个ID来发起相关的操作，如取消分片上传、查询分片上传等。
			String uploadId = result.getUploadId();
			/* 步骤2：上传分片。*/
			// partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
			List<PartETag> partETags =  new ArrayList<PartETag>();
			// 计算文件有多少个分片。
			final long partSize = 1 * 1024 * 1024L;   // 1MB
			final File sampleFile = localFile;
			long fileLength = sampleFile.length();
			int partCount = (int) (fileLength / partSize);
			if (fileLength % partSize != 0) {
				partCount++;
			}
			// 遍历分片上传。
			for (int i = 0; i < partCount; i++) {
				long startPos = i * partSize;
				long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
				InputStream instream = new FileInputStream(sampleFile);
				// 跳过已经上传的分片。
				instream.skip(startPos);
				UploadPartRequest uploadPartRequest = new UploadPartRequest();
				uploadPartRequest.setBucketName(bucketName);
				uploadPartRequest.setKey(objectName);
				uploadPartRequest.setUploadId(uploadId);
				uploadPartRequest.setInputStream(instream);
				// 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100KB。
				uploadPartRequest.setPartSize(curPartSize);
				// 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出这个范围，OSS将返回InvalidArgument的错误码。
				uploadPartRequest.setPartNumber( i + 1);
				// 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
				UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
				// 每次上传分片之后，OSS的返回结果会包含一个PartETag。PartETag将被保存到partETags中。
				partETags.add(uploadPartResult.getPartETag());
			}
			/* 步骤3：完成分片上传。*/
			// 排序。partETags必须按分片号升序排列。
			Collections.sort(partETags, new Comparator<PartETag>() {
				@Override
				public int compare(PartETag p1, PartETag p2) {
					return p1.getPartNumber() - p2.getPartNumber();
				}
			});
			// 在执行该操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
			CompleteMultipartUploadRequest completeMultipartUploadRequest =
					new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);



			if (null != callbackUrl){
				// 设置上传成功回调，参数为Callback类型。
				completeMultipartUploadRequest.setCallback(OssCommonUtils.buildCallback(callbackUrl,objectName));
			}

			ossClient.completeMultipartUpload(completeMultipartUploadRequest);


			ObjectMetadata objectMeta = ossClient.getObjectMetadata(bucketName, objectName);
			Map<String, Object> data = new HashMap<>();
			data.put("contentType", objectMeta.getContentType());
			data.put("contentLength", objectMeta.getContentLength());
			data.put("lastModified", objectMeta.getLastModified());
			jsonResult.setData(data);

			jsonResult.setSuccess(true);
			jsonResult.setCode(1);


			// 关闭OSSClient。
			ossClient.shutdown();
		} catch (OSSException oe) {
			jsonResult.setData(OssCommonUtils.extractOSSException(oe));
		} catch (ClientException ce) {
			jsonResult.setData(OssCommonUtils.extractClientException(ce));
		} catch (IOException e) {
			jsonResult.setData(OssCommonUtils.extractIOException(e));
		} finally {
			//关闭客服端释放资源
			if (ossClient != null) {
				ossClient.shutdown();
			}
		}
		return jsonResult;
	}


}
